El bloque try/except de Python tiene cuatro cláusulas posibles, y cada una tiene un propósito diferente. Conocerlas bien evita la trampa más habitual: capturar excepciones demasiado amplias que silencian errores reales.
try/except básico
def dividir(a, b):
try:
return a / b
except ZeroDivisionError:
print("Error: división por cero")
return None
print(dividir(10, 2)) # 5.0
print(dividir(10, 0)) # Error: división por cero ? None
Capturar múltiples excepciones
def parsear_entero(texto):
try:
return int(texto)
except (ValueError, TypeError) as e:
# Tupla de tipos para capturar varios a la vez
print(f"No se pudo parsear '{texto}': {e}")
return None
print(parsear_entero("42")) # 42
print(parsear_entero("abc")) # No se pudo parsear 'abc': invalid literal...
print(parsear_entero(None)) # No se pudo parsear 'None': int() argument...
La cláusula else: solo si no hubo excepción
El bloque else se ejecuta solo si el bloque try terminó sin lanzar ninguna excepción. Separa el código de éxito del de manejo de errores:
import json
def cargar_config(ruta):
try:
with open(ruta) as f:
datos = json.load(f)
except FileNotFoundError:
print(f"Fichero no encontrado: {ruta}")
return {}
except json.JSONDecodeError as e:
print(f"JSON inválido en {ruta}: {e}")
return {}
else:
# Solo llegamos aquí si open() y json.load() funcionaron
print(f"Config cargada: {len(datos)} claves")
return datos
La cláusula finally: siempre se ejecuta
finally se ejecuta siempre: haya excepción o no, haya return dentro del try o no. Es el lugar correcto para liberar recursos:
def leer_fichero(ruta):
f = None
try:
f = open(ruta, encoding="utf-8")
return f.read()
except OSError as e:
print(f"Error al leer: {e}")
return ""
finally:
if f:
f.close()
print("Fichero cerrado") # siempre
# En la práctica, usa 'with' que hace esto automáticamente
def leer_fichero_mejor(ruta):
try:
with open(ruta, encoding="utf-8") as f:
return f.read()
except OSError as e:
print(f"Error al leer: {e}")
return ""
Re-raise: propagar la excepción
import logging
def procesar_pago(importe):
try:
# simulación
if importe > 10000:
raise ValueError("Importe demasiado alto")
return {"estado": "OK", "importe": importe}
except ValueError as e:
logging.error(f"Error de validación: {e}")
raise # re-lanza la excepción original con el traceback completo
try:
procesar_pago(50000)
except ValueError as e:
print(f"El pago falló: {e}")
Jerarquía de excepciones y los antipatrones
# MAL: captura demasiado amplia oculta bugs
try:
resultado = calcular_algo()
except Exception: # o bare 'except:'
pass # silencia TODOS los errores, incluso bugs de código
# MAL: capturar y no hacer nada útil
try:
conectar()
except Exception as e:
print(e) # sin logging, sin re-raise, sin manejo real
# BIEN: captura específica + acción útil
try:
conectar()
except ConnectionRefusedError:
logger.error("No se pudo conectar al servidor")
raise SystemExit(1)
La regla: captura siempre el tipo de excepción más específico posible. Usa else para separar el camino feliz del manejo de errores. Usa finally para limpieza. Evita capturar Exception o usar except: desnudo salvo en el nivel más alto de tu programa.
